package com.globefrog.framework.web;

import com.globefrog.framework.common.exception.AuthenticationException;
import com.globefrog.framework.common.exception.BusinessException;
import com.globefrog.framework.common.exception.InvalidRequestException;
import com.globefrog.framework.common.exception.ResourceExistException;
import com.globefrog.framework.common.exception.ResourceNotFoundException;
import com.globefrog.framework.log.util.ExceptionUtils;
import com.globefrog.framework.web.response.BaseResponse;
import com.google.common.collect.Maps;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ValidationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeStacktrace;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;

/**
 * 基础Rest控制类
 * 如果项目需要,可自定义异常错误处理类,继承BaseRestController
 * 通过@RestControllerAdvice(basePackageClasses = {启动类.class})指定扫描的包路径
 *
 * @author : caijinbang
 * @date : Create in 2021/5/21
 */
@Slf4j
public class BaseRestController {

  @Autowired
  private ServerProperties serverProperties;

  public BaseRestController() {

  }

  @ExceptionHandler
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  @ResponseBody
  public BaseResponse<Void> error(HttpServletRequest request, Throwable e) {
    return createErrorResponse(request, e, HttpStatus.INTERNAL_SERVER_ERROR);
  }

  /**
   * 资源不存在异常
   *
   * @param request 请求
   * @param e       异常
   * @return 基础返回
   */
  @ExceptionHandler({ResourceNotFoundException.class})
  @ResponseStatus(HttpStatus.NOT_FOUND)
  @ResponseBody
  public BaseResponse<Void> notFound(HttpServletRequest request, ResourceNotFoundException e) {
    return createErrorResponse(request, e, HttpStatus.NOT_FOUND);
  }

  /**
   * 资源已存在
   *
   * @param request 请求
   * @param e       异常
   * @return 基础返回
   */
  @ExceptionHandler({ResourceExistException.class})
  @ResponseStatus(HttpStatus.CONFLICT)
  @ResponseBody
  public BaseResponse<Void> hasExisted(HttpServletRequest request, ResourceExistException e) {
    return createErrorResponse(request, e, HttpStatus.CONFLICT);
  }

  /**
   * 验证异常,非法参数请求
   *
   * @param request 请求
   * @param e       异常
   * @return 基础返回
   */
  @ExceptionHandler({ValidationException.class, InvalidRequestException.class})
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ResponseBody
  public BaseResponse<Void> validationError(HttpServletRequest request, Throwable e) {
    return createErrorResponse(request, e, HttpStatus.BAD_REQUEST);
  }

  /**
   * 方法参数未验证异常
   *
   * @param request 请求
   * @param e       异常
   * @return 基础返回
   */
  @ExceptionHandler({MethodArgumentNotValidException.class})
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ResponseBody
  public BaseResponse<Void> validationError(HttpServletRequest request,
      MethodArgumentNotValidException e) {
    Map<String, String> errors = Maps.newHashMap();
    BindingResult bindingResult = e.getBindingResult();
    List<FieldError> fieldErrors = bindingResult.getFieldErrors();
    for (FieldError fieldError : fieldErrors) {
      errors.put(fieldError.getField(), fieldError.getDefaultMessage());
    }
    BaseResponse<Void> baseResponse = BaseResponse
        .error(HttpStatus.BAD_REQUEST, com.globefrog.common.util.JsonUtil.toJSON(errors));
    // 是否包括堆栈信息
    if (isIncludeStackTrace(request)) {
      baseResponse.setStackTrace(ExceptionUtils.stackTrace(e));
    }
    return baseResponse;
  }

  /**
   * 认证异常
   *
   * @param request 请求
   * @param e       异常
   * @return 基础返回
   */
  @ExceptionHandler({AuthenticationException.class})
  @ResponseStatus(HttpStatus.UNAUTHORIZED)
  @ResponseBody
  public BaseResponse<Void> validationError(HttpServletRequest request, AuthenticationException e) {
    return createErrorResponse(request, e, HttpStatus.UNAUTHORIZED);
  }

  /**
   * 业务异常
   *
   * @param request 请求
   * @param e       异常
   * @return 基础返回
   */
  @ExceptionHandler({BusinessException.class})
  @ResponseStatus(HttpStatus.OK)
  @ResponseBody
  public BaseResponse<Void> businessError(HttpServletRequest request, BusinessException e) {
    return createErrorResponse(request, e);
  }

  @ExceptionHandler({
      HttpRequestMethodNotSupportedException.class,
      HttpMediaTypeNotSupportedException.class,
      HttpMediaTypeNotAcceptableException.class,
      ServletRequestBindingException.class,
      TypeMismatchException.class,
      HttpMessageNotReadableException.class,
      MissingServletRequestPartException.class,
      BindException.class,
      NoHandlerFoundException.class}
  )
  @ResponseBody
  public BaseResponse<Void> handleSpringException(HttpServletRequest request,
      HttpServletResponse response, Exception ex) throws Exception {
    HttpStatus status;
    if (ex instanceof HttpRequestMethodNotSupportedException) {
      // 方法不支持
      status = HttpStatus.METHOD_NOT_ALLOWED;
    } else if (ex instanceof HttpMediaTypeNotSupportedException) {
      // 不支持的媒体格式
      status = HttpStatus.UNSUPPORTED_MEDIA_TYPE;
    } else if (ex instanceof HttpMediaTypeNotAcceptableException) {
      // 不可接受的媒体类型,例: 要求json, 实际xml
      status = HttpStatus.NOT_ACCEPTABLE;
    } else if (ex instanceof NoHandlerFoundException) {
      // 找不到处理的类
      status = HttpStatus.NOT_FOUND;
    } else {
      if (!(ex instanceof ServletRequestBindingException) && !(ex instanceof TypeMismatchException)
          && !(ex instanceof HttpMessageNotReadableException)
          && !(ex instanceof MissingServletRequestPartException)
          && !(ex instanceof BindException)) {
        throw ex;
      }
      status = HttpStatus.BAD_REQUEST;
    }
    response.setStatus(status.value());
    return createErrorResponse(request, ex, status);
  }

  private BaseResponse<Void> createErrorResponse(HttpServletRequest request, Throwable e,
      HttpStatus httpStatus) {
    BaseResponse<Void> baseResponse = BaseResponse.error(httpStatus, e.getMessage());
    setErrorMessage(request, baseResponse, e);
    return baseResponse;
  }

  private BaseResponse<Void> createErrorResponse(HttpServletRequest request, BusinessException e) {
    BaseResponse<Void> baseResponse = new BaseResponse<>();
    baseResponse.setCode(e.getErrorCode());
    setErrorMessage(request, baseResponse, e);
    return baseResponse;
  }

  private void setErrorMessage(HttpServletRequest request, BaseResponse<Void> baseResponse,
      Throwable e) {
    // 是否消息有值
    if (StringUtils.hasText(e.getMessage())) {
      baseResponse.setMessage(e.getMessage());
    }
    // 是否需要打印堆栈
    if (isIncludeStackTrace(request)) {
      baseResponse.setStackTrace(ExceptionUtils.stackTrace(e));
    }

  }

  private boolean isIncludeStackTrace(HttpServletRequest request) {
    if (this.serverProperties == null) {
      return false;
    } else {
      IncludeStacktrace include = this.serverProperties.getError().getIncludeStacktrace();
      if (include == IncludeStacktrace.ALWAYS) {
        return true;
      } else {
        return include == IncludeStacktrace.ON_TRACE_PARAM && getTraceParameter(request);
      }
    }
  }

  protected boolean getTraceParameter(HttpServletRequest request) {
    String parameter = request.getParameter("trace");
    if (parameter == null) {
      return false;
    } else {
      return !"false".equalsIgnoreCase(parameter);
    }
  }
}
